//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.IdentityModel.Tokens;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security.Tokens;
using System.Xml;
using System.Globalization;

namespace Shp.Security.BrokeredReceiver
{    
    /// <summary>
    /// A class that represents a RequestSecurityToken message per February 2005 WS-Trust
    /// </summary>
    [ComVisible(false)]
    public class RequestSecurityToken : RequestSecurityTokenBase
    {
        private SecurityToken requestorEntropy;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:RequestSecurityToken"/> class.
        /// </summary>
        public RequestSecurityToken() : 
            this(String.Empty, String.Empty, 0, null, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:RequestSecurityToken"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="tokenType">Type of the token.</param>
        /// <param name="keySize">Size of the key.</param>
        /// <param name="appliesTo">The applies to.</param>
        /// <param name="entropy">The entropy.</param>
        public RequestSecurityToken(string context, 
                                    string tokenType, 
                                    int keySize,                                     
                                    EndpointAddress appliesTo,
                                    SecurityToken entropy ) : 
            base ( context, tokenType, keySize, appliesTo ) 
        {
            this.requestorEntropy = entropy;
        }

        /// <summary>
        /// The SecurityToken representing entropy provided by the requestor.
        /// Null if the requestor did not provide entropy
        /// </summary>
        public SecurityToken RequestorEntropy
        {
            get { return requestorEntropy; }
            set { requestorEntropy = value; }
        }
    
        /// <summary>
        /// Reads a wst:RequestSecurityToken element, its attributes and children and 
        /// creates a RequestSecurityToken instance with the appropriate values
        /// </summary>
        /// <param name="reader">An XmlReader positioned on wst:RequestSecurityToken</param>
        /// <returns>A RequestSecurityToken instance, initialized with the data read from the XmlReader</returns>
        static public RequestSecurityToken CreateFrom(XmlReader reader)
        {
            return ProcessRequestSecurityTokenElement(reader);        
        }

        // Methods of BodyWriter
        /// <summary>
        /// Writes out an XML representation of the instance.
        /// Provided here for completeness. Not actually called by this sample.
        /// </summary>
        /// <param name="writer">The writer to be used to write out the XML content</param>
        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement(Constants.TrustFeb2005.RequestSecurityToken, Constants.TrustFeb2005.Namespace);

            if (this.TokenType != null && this.TokenType.Length > 0)
            {
                writer.WriteStartElement(Constants.TrustFeb2005.TokenType, Constants.TrustFeb2005.Namespace);
                writer.WriteString(this.TokenType);
                writer.WriteEndElement();
            }

            if (this.AppliesTo != null)
            {
                writer.WriteStartElement(Constants.Policy.AppliesTo, Constants.Policy.NamespaceUri);
                this.AppliesTo.WriteTo(AddressingVersion.WSAddressing10, writer);
                writer.WriteEndElement(); 
            }

            if (this.requestorEntropy!=null)
            {
                writer.WriteStartElement(Constants.TrustFeb2005.Entropy, Constants.TrustFeb2005.Namespace);
                BinarySecretSecurityToken bsst = this.requestorEntropy as BinarySecretSecurityToken;
                if (bsst!=null)
                {
                    writer.WriteStartElement(Constants.TrustFeb2005.BinarySecret, Constants.TrustFeb2005.Namespace);
                    byte[] key = bsst.GetKeyBytes();
                    writer.WriteBase64(key, 0, key.Length);
                    writer.WriteEndElement(); 
                }
                writer.WriteEndElement(); 
            }

            if (this.KeySize > 0 )
            {
                writer.WriteStartElement(Constants.TrustFeb2005.KeySize, Constants.TrustFeb2005.Namespace);
                writer.WriteValue(this.KeySize);
                writer.WriteEndElement(); 
            }

            writer.WriteEndElement();
        }

        /// <summary>
        /// Reads the wst:RequestSecurityToken element
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>
        /// A RequestSecurityToken instance, initialized with the data read from the XmlReader
        /// </returns>
        private static RequestSecurityToken ProcessRequestSecurityTokenElement(XmlReader reader)
        {
            if (reader == null)
            {
                FederationUtilities.ThrowTrustFault(Properties.Resources.ArgumentNullException, "reader");
            }

            if (reader.IsEmptyElement)
            {
                FederationUtilities.ThrowTrustFault(Properties.Resources.EmptyElement, "RequestSecurityToken", "RequestSecurityToken");
            }

            int initialDepth = reader.Depth;
            string context = reader.GetAttribute(Constants.TrustFeb2005.Context, String.Empty);
            string tokenType = String.Empty;
            int keySize = 0;
            EndpointAddress appliesTo = null;
            SecurityToken entropy = null;

            while (reader.Read())
            {                
                if (XmlNodeType.Element == reader.NodeType)
                {
                    if (Constants.TrustFeb2005.Namespace == reader.NamespaceURI)
                    {
                        if (Constants.TrustFeb2005.TokenType == reader.LocalName &&
                            !reader.IsEmptyElement)
                        {
                            reader.Read();
                            tokenType = reader.ReadContentAsString();
                        }
                        else if (Constants.TrustFeb2005.KeySize == reader.LocalName &&
                                 !reader.IsEmptyElement)
                        {
                            reader.Read();
                            keySize = reader.ReadContentAsInt();                            
                        }
                        else if (Constants.TrustFeb2005.Entropy == reader.LocalName &&
                                 !reader.IsEmptyElement)
                        {
                            entropy = ProcessEntropyElement(reader);
                        }
                    }
                    else if (Constants.Policy.NamespaceUri == reader.NamespaceURI)
                    {
                        if (Constants.Policy.AppliesTo == reader.LocalName &&
                            !reader.IsEmptyElement)
                        {
                            appliesTo = ProcessAppliesToElement(reader);
                        }
                    }
                }

                if (Constants.TrustFeb2005.RequestSecurityToken == reader.LocalName &&
                    Constants.TrustFeb2005.Namespace == reader.NamespaceURI &&
                    reader.Depth == initialDepth &&
                    XmlNodeType.EndElement == reader.NodeType)
                {
                    break;
                }
            }

            return new RequestSecurityToken(context, tokenType, keySize, appliesTo, entropy);
        }

        /// <summary>
        /// Reads a wst:Entropy element and constructs a SecurityToken
        /// Assumes that the provided entropy will never be more than 1Kb in size
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>
        /// A SecurityToken containing the entropy value
        /// </returns>
        private static SecurityToken ProcessEntropyElement(XmlReader reader)
        {
            if (reader == null)
            {
                FederationUtilities.ThrowTrustFault(Properties.Resources.ArgumentNullException, "reader");
            }

            if (reader.IsEmptyElement)
            {
                FederationUtilities.ThrowTrustFault(Properties.Resources.EmptyElement, "Entropy", "SecurityToken");
            }

            int initialDepth = reader.Depth;
            SecurityToken st = null;

            while (reader.Read())
            {             
                if (Constants.TrustFeb2005.BinarySecret == reader.LocalName &&
                    Constants.TrustFeb2005.Namespace == reader.NamespaceURI &&
                    !reader.IsEmptyElement &&
                    XmlNodeType.Element == reader.NodeType)
                {
                    byte[] temp = new byte[1024];
                    reader.Read();
                    int nBytes = reader.ReadContentAsBase64(temp, 0, temp.Length);
                    byte[] entropy = new byte[nBytes];

                    for (int i = 0; i < nBytes; i++)
                    {
                        entropy[i] = temp[i];
                    }

                    st = new BinarySecretSecurityToken(entropy);
                }

                if (Constants.TrustFeb2005.Entropy == reader.LocalName &&
                    Constants.TrustFeb2005.Namespace == reader.NamespaceURI &&
                    reader.Depth == initialDepth &&
                    XmlNodeType.EndElement == reader.NodeType)
                {
                    break;
                }
            }

            return st;
        }

        /// <summary>
        /// Reads a wsp:AppliesTo element
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>An EndpointAddress</returns>
        private static EndpointAddress ProcessAppliesToElement(XmlReader reader)
        {
            if (reader == null)
            {
                FederationUtilities.ThrowTrustFault(Properties.Resources.ArgumentNullException, "reader");
            }

            if (reader.IsEmptyElement)
            {
                FederationUtilities.ThrowTrustFault(Properties.Resources.EmptyElement, "AppliesTo", "EndpointAddress");
            }

            int initialDepth = reader.Depth;
            EndpointAddress endpointAddress = null;

            while (reader.Read())
            {
                if (Constants.Addressing.EndpointReference == reader.LocalName &&
                    Constants.Addressing.NamespaceUri == reader.NamespaceURI &&
                    !reader.IsEmptyElement &&
                    XmlNodeType.Element == reader.NodeType)
                {
                    DataContractSerializer dcs = new DataContractSerializer(typeof(EndpointAddress10));
                    EndpointAddress10 endpointAddress10 = (EndpointAddress10)dcs.ReadObject(reader, false);
                    endpointAddress = endpointAddress10.ToEndpointAddress();
                }

                if (Constants.Policy.AppliesTo == reader.LocalName &&
                    Constants.Policy.NamespaceUri == reader.NamespaceURI &&
                    reader.Depth == initialDepth &&
                    XmlNodeType.EndElement == reader.NodeType)
                {
                    break;
                }
            }

            return endpointAddress;
        }
    }
}
